using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Deepcode.Flux.Core.Data;

namespace Deepcode.Flux.Core.Systems.CMS
{
	public interface IContentQueryPart
	{
		string Render( ref List<SqlParameter> parameters, ref List<String> extrajoins );
	}

	public enum ContentQueryPartConjoinEnum
	{
		AND,
		OR
	}
	public class ContentQueryPartDecorator
	{
		private IContentQueryPart _query = null;
		private ContentQueryPartConjoinEnum _conjoin = ContentQueryPartConjoinEnum.AND;

		public IContentQueryPart Query{ get{ return _query; }}
		public ContentQueryPartConjoinEnum Conjoin{ get{ return _conjoin; }}

		public ContentQueryPartDecorator( IContentQueryPart query, ContentQueryPartConjoinEnum conjoin )
		{
			_query = query;
			_conjoin = conjoin;
		}
	}

	public class ContentQueryCriteria : IContentQueryPart
	{
		private List<ContentQueryPartDecorator> items = new List<ContentQueryPartDecorator>();

		public void AND(IContentQueryPart newpart)
		{
			items.Add( new ContentQueryPartDecorator( newpart, ContentQueryPartConjoinEnum.AND ));
		}
		public void OR(IContentQueryPart newpart)
		{
			items.Add( new ContentQueryPartDecorator( newpart, ContentQueryPartConjoinEnum.OR ));
		}
		public string Render( ref List<SqlParameter> parameters, ref List<String> extrajoins  )
		{
			if( items.Count < 1 ) return "";

			string output = "";
			foreach (ContentQueryPartDecorator item in items)
			{
				if (output != "")
				{
					if( item.Conjoin == ContentQueryPartConjoinEnum.AND ) output += "AND ";
					if( item.Conjoin == ContentQueryPartConjoinEnum.OR ) output += "OR ";
				}

				output += item.Query.Render( ref parameters, ref extrajoins );
			}
			return String.Format("({0})", output);
		}
	}
	public enum ContentQueryOperator
	{
		EQ, NEQ, LT, GT, LTE, GTE, LIKE
	}
	public class ContentQueryOperatorHelper
	{
		public static string GetSQLFor(ContentQueryOperator op)
		{
			switch (op)
			{
				case ContentQueryOperator.EQ:
					return "=";
				case ContentQueryOperator.NEQ:
					return "<>";
				case ContentQueryOperator.LT:
					return "<";
				case ContentQueryOperator.GT:
					return ">";
				case ContentQueryOperator.GTE:
					return ">=";
				case ContentQueryOperator.LTE:
					return "<=";
				case ContentQueryOperator.LIKE:
					return "LIKE";
			}

			throw new NotSupportedException();
		}
	}
	public class ParameterHelper
	{
		public static string GetUniqueParameter(List<SqlParameter> parameters, string field, int counter)
		{
			// Get a unique name
			string uniqueName = String.Format("@{0}{1}", field, counter);
			// See if it exists
			foreach( SqlParameter p in parameters )
				if (p.ParameterName == uniqueName)
				{
					// It exists - add 1 to counter and try again
					return GetUniqueParameter( parameters, field, counter+1 );
				}

			return uniqueName;
		}
	}
	public class SQLFieldTypeInfo
	{
		private SqlDbType _type;
		private int _length = 0;

		public SqlDbType Type { get { return _type; } }
		public int Length { get { return _length; } }

		public SQLFieldTypeInfo(SqlDbType type, int len)
		{
			_type = type;
			_length = len;
		}
	}
	public class SqlFieldHelper
	{
		public static SQLFieldTypeInfo GetTypeForObject(object o)
		{
			if( o is string )
				return new SQLFieldTypeInfo(SqlDbType.NVarChar, 1000);
			if( (o is int) || (o is long) )
				return new SQLFieldTypeInfo(SqlDbType.Int, 4);
			if( o is System.DateTime)
				return new SQLFieldTypeInfo(SqlDbType.DateTime, 8);
			if( o is bool )
				return new SQLFieldTypeInfo(SqlDbType.Bit, 1 );
			if( o is Guid )
				return new SQLFieldTypeInfo(SqlDbType.UniqueIdentifier, 16);
			if( o is double )
				return new SQLFieldTypeInfo(SqlDbType.Decimal, 8);
			if( o is float )
				return new SQLFieldTypeInfo(SqlDbType.Float, 12);

			throw new NotSupportedException();
		}
		public static SQLFieldTypeInfo GetTypeForStaticField( ContentStaticField field )
		{
			switch (field)
			{
				case ContentStaticField.ID:
				case ContentStaticField.ParentID:
					return new SQLFieldTypeInfo(SqlDbType.Int, 4);
				case ContentStaticField.Title:
					return new SQLFieldTypeInfo(SqlDbType.NVarChar, 500);
				case ContentStaticField.Name:
					return new SQLFieldTypeInfo(SqlDbType.NVarChar, 100);
				case ContentStaticField.Path:
					return new SQLFieldTypeInfo(SqlDbType.NVarChar, 4000);
				case ContentStaticField.CreatedDateAndTime:
				case ContentStaticField.ModifiedDateAndTime:
					return new SQLFieldTypeInfo(SqlDbType.DateTime, 8);
				case ContentStaticField.ContentType:
					return new SQLFieldTypeInfo(SqlDbType.NVarChar, 250);
			}

			throw new NotSupportedException();
		}
		public static string GetSQLFieldNameForStaticField( ContentStaticField field )
		{
			switch (field)
			{
				case ContentStaticField.ID:						return "c.PK_ID";
				case ContentStaticField.ParentID:				return "c.FK_ParentID";
				case ContentStaticField.Title:					return "c.NodeTitle";
				case ContentStaticField.Name:					return "c.NodeName";
				case ContentStaticField.Path:					return "c.NodePath";
				case ContentStaticField.CreatedDateAndTime:		return "c.CreatedDT";
				case ContentStaticField.ModifiedDateAndTime:	return "c.LastModifiedDT";
				case ContentStaticField.ContentType:			return "c.FK_STypeID";
			}

			throw new NotSupportedException();
		}
	
	}

	public class XMLFieldMatch : IContentQueryPart
	{
		private string _fieldName = "";
		private ContentQueryOperator _operator = ContentQueryOperator.EQ;
		private object _matchValue = "";


		public XMLFieldMatch( string fieldName, ContentQueryOperator oper, object value )
		{
			_fieldName = fieldName;
			_operator = oper;
			_matchValue = value;
		}

		public string Render(ref List<SqlParameter> parameters, ref List<String> extrajoins)
		{
			// Get unique parameter name
			string parameterName = ParameterHelper.GetUniqueParameter( parameters, _fieldName, 1 );
			// Get field information
			SQLFieldTypeInfo field = SqlFieldHelper.GetTypeForObject( _matchValue );
			string fieldTypeCast = field.Type.ToString();
			if( field.Type == SqlDbType.NVarChar ) fieldTypeCast = string.Format("{0}({1})", field.Type.ToString(), field.Length);

			string output = "";
			output += String.Format("(XMLTABLE.ITEMS.query('.').value('(/xml/{0})[1]', '{1}') {2} {3})", 
				_fieldName, 
				fieldTypeCast,
				ContentQueryOperatorHelper.GetSQLFor( _operator ),
				parameterName);

			parameters.Add( SQLUtility.ParameterIn( parameterName, field.Type, field.Length, _matchValue ) );
			
			return output;
		}
	}

	public enum ContentStaticField
	{
		ID,
		ParentID,
		Title,
		Name,
		Path,
		CreatedDateAndTime,
		ModifiedDateAndTime,
		ContentType
	}
	public class StaticFieldMatch : IContentQueryPart
	{
		private ContentStaticField _field = ContentStaticField.ID;
		private ContentQueryOperator _operator = ContentQueryOperator.EQ;
		private object _matchValue = "";


		public StaticFieldMatch( ContentStaticField field, ContentQueryOperator oper, object value )
		{
			_field = field;
			_operator = oper;
			_matchValue = value;
		}

		public string Render(ref List<SqlParameter> parameters, ref List<String> extrajoins)
		{
			// Get unique parameter name
			string parameterName = ParameterHelper.GetUniqueParameter( parameters, _field.ToString(), 1 );
			// Get field information
			SQLFieldTypeInfo field = SqlFieldHelper.GetTypeForStaticField(_field);
			
			string output = "";
			output += String.Format("({0} {1} {2})", 
				SqlFieldHelper.GetSQLFieldNameForStaticField(_field), 
				ContentQueryOperatorHelper.GetSQLFor( _operator ),
				parameterName);

			parameters.Add( SQLUtility.ParameterIn( parameterName, field.Type, field.Length, _matchValue ) );
			
			return output;
		}
	}

	public class TaxonomyMatch : IContentQueryPart
	{
		private int _taxonomyMatch = -1;

		public TaxonomyMatch(int TaxonomyID)
		{
			_taxonomyMatch = TaxonomyID;
		}
		public TaxonomyMatch(string TaxonomyPath)
		{
			DataTaxonomy.DataTaxonomyTree tree = new Deepcode.Flux.Core.Systems.CMS.DataTaxonomy.DataTaxonomyTree();
			DataTaxonomy.DataTaxonomyNode node = tree.GetPath( TaxonomyPath );
			if( node == null ) _taxonomyMatch = -1;
			else _taxonomyMatch = node.PK_ID;
		}

		public string Render(ref List<SqlParameter> parameters, ref List<String> extrajoins)
		{
			string extrajoin = String.Format(
				"INNER JOIN ContentTaxonomy ct{0} ON (ct{0}.FK_ContentID=c.PK_ID AND ct{0}.FK_DataTaxonomyID={1})",
				extrajoins.Count + 1,
				_taxonomyMatch);

			extrajoins.Add( extrajoin );
			return "1=1";
		}
	}
}
